#include "commonFunc.h"



using std::string;
using std::endl;
using std::cerr;
using std::list;

GLuint loadGLTex(string pathToImg)
{
    GLuint texture;
    SDL_Surface* surface;
    SDL_Surface* surface2;
    GLenum texture_format;
    GLint nOfColors;
    if ((surface2 = IMG_Load(pathToImg.c_str())))
    {
        surface = SDL_DisplayFormatAlpha(surface2);
        if ((surface->w & (surface->w-1)) != 0) //Bitwise AND function to determine if dimension is a power of 2
        {
            cerr << "Texture " << pathToImg << " is not a power of 2" << endl;
            exit(1);
        }
        if ((surface->h & (surface->h-1)) != 0) //Bitwise AND function to determine if dimension is a power of 2
        {
            cerr << "Texture " << pathToImg << " is not a power of 2" << endl;
            exit(1);
        }
        nOfColors = surface->format->BytesPerPixel;
        if (nOfColors == 4) //Contains transparency
        {
            if (surface->format->Rmask == 0x000000ff)
            {
                texture_format = GL_RGBA;
            } else {
                texture_format = GL_BGRA;
            }
        } else if (nOfColors == 3) {
            if (surface->format->Rmask == 0x000000ff)
            {
                texture_format = GL_RGB;
            } else {
                texture_format = GL_BGR;
            }
        } else {
            cerr << "Texture " << pathToImg << " is not truecolor... FAILED" << endl;
            exit(1);
        }

        glGenTextures(1, &texture); //Create a handle to texture in OpenGL
	
        glBindTexture(GL_TEXTURE_2D, texture); //Set the texture type

        //Set the stretching parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        //Copy image data to openGL texture handle
        glTexImage2D(GL_TEXTURE_2D, 0, nOfColors, surface->w, surface->h, 0, texture_format, GL_UNSIGNED_BYTE, surface->pixels);
        //Free surface from SDL since the image is now in openGL
        if (surface)
        {
            SDL_FreeSurface( surface );
            SDL_FreeSurface( surface2);
        }

    } else {
        cerr << "Failed to load texture: " << pathToImg << endl;
        exit(1);
    }
    return texture;
}

GLuint renderChar(char c)
{
    GLuint texture;
    SDL_Color color;
    color.r = 255;
    color.g = 255;
    color.b = 255;
    stringstream str;
    str << c;
    string Text = str.str();
    static TTF_Font* font = TTF_OpenFont(FONT,32);
    
    SDL_Surface *Message = TTF_RenderText_Blended(font, Text.c_str(), color);
    
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Message->w, Message->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, Message->pixels);
    
    SDL_FreeSurface(Message);
    
    return texture;
}

SDL_Surface* initScreen(ptrContainer & dcontainer)
{
//INIT OPENGL
    if ( SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0 ) {
        printf("Unable to initialize SDL: %s\n", SDL_GetError());
        exit(1);
    }


    

    //SET UP AUDIO
    int audio_rate = 22050;
    Uint16 audio_format = AUDIO_S16SYS;
    int audio_channels = 2;
    int audio_buffers = 4096;
 
    if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0) {
        fprintf(stderr, "Unable to initialize audio: %s\n", Mix_GetError());
        exit(1);
    }


    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
    //CREATE SCREEN with the resolution of the arcade box
#if FULLSCREEN == 1
    SDL_Surface* screen = SDL_SetVideoMode( 1024, 768, 32, SDL_OPENGL | SDL_FULLSCREEN);
#endif
#if FULLSCREEN == 0
    SDL_Surface* screen = SDL_SetVideoMode( 1024, 768, 32, SDL_OPENGL);
#endif


    if (TTF_Init())
    {
        printf("FAILURE TO LOAD SDLTTF: %s\n", TTF_GetError());
        exit(1);
    }


//SET OPENGL INITIAL STATE
    glEnable( GL_TEXTURE_2D );
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //Black clear color... opengl needs floats for everything
    glViewport(0,0,1024,768); //Create the virtual opengl world, upper left is 0,0 bottom right is 1024,768
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho(0.0f, 1024, 768, 0.0f, -1.0f, 1.0f);

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    return screen;
}

void handleEvents(bool & ret, int & numToWave, ptrContainer & dcontainer)
{
    static int passcount = 0;
    static unsigned char flags = 0x00;
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
        if (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN)
        {
            passcount = 0;
            updateArrowFlags(event, flags);
            handleKeysNormal(dcontainer, event, ret, numToWave, flags, passcount);	    
        }
    }
    
    if ((passcount <= 0) && (flags != 0))
    {
        /*down = 0x01
          up = 0x02
          left = 0x04
          right = 0x08*/
        if ((flags & 0x01) != 0) //down
            dcontainer.m->move(0,1);
        if ((flags & 0x02) != 0) //up
            dcontainer.m->move(0,-1);
        if ((flags & 0x04) != 0) //left
            dcontainer.m->move(-1,0);
        if ((flags & 0x08) != 0) //right
            dcontainer.m->move(1,0);
        passcount = 7;
    }
    if (passcount > 0)
        passcount--;

}
void updateArrowFlags(SDL_Event & event, unsigned char & flags)
{
    switch (event.key.keysym.sym)
    {
      case SDLK_LEFT:
	    flags = flags ^ 0x04;
	    break;
      case SDLK_RIGHT:
	    flags = flags ^ 0x08;
	    break;
      case SDLK_UP:
	    flags = flags ^ 0x02;
	    break;
      case SDLK_DOWN:
	    flags = flags ^ 0x01;
        break;
      default:
	    break;
    }
}
bool handleKeysNormal(ptrContainer & dcontainer, SDL_Event & event, bool & ret, int & numToWave, unsigned char & flags, int & passcount)
{
    bool handled = true; //True if keys should not continue down the list
    switch (event.key.keysym.sym)
    {
      case SDLK_ESCAPE:
	    ret = true;
        dcontainer.sw->changestate(2);
	    break;
      case SDLK_e:
      {
          dcontainer.g->timetonextround = 1;
          numToWave += 5;
      }
      break;
      case SDLK_q:
        if (dcontainer.curTower > 0)
            dcontainer.curTower--;
        break;
      case SDLK_w:
        if (dcontainer.curTower < NUMOFTOWERS - 1)
            dcontainer.curTower++;
        break;
      case SDLK_1: //Money Cheatcode
	    if (event.type == SDL_KEYDOWN)
            dcontainer.theplayer->grantMoney(20);
	    break;
      case SDLK_2: //Health Cheatcode
	    if (event.type == SDL_KEYDOWN)
            dcontainer.theplayer->curhp = dcontainer.theplayer->maxhp;
	    break;
      case SDLK_SPACE:
	    if (event.type == SDL_KEYDOWN && dcontainer.m->canbuild() && dcontainer.theplayer->purchase(dcontainer.towerCost[dcontainer.curTower]))
	    {
            for (int cury = dcontainer.m->y; cury <= dcontainer.m->y+1; cury++)
            {
                for (int curx = dcontainer.m->x; curx <= dcontainer.m->x+1; curx++)
                {
                    if (cury >= 0 && cury < dcontainer.m->height && curx >= 0 && curx < dcontainer.m->width)
                    {
                        dcontainer.m->walkgrid[cury][curx] = false;
                        dcontainer.p->data[cury][curx].setw(false);
                    }
                }
            }
            createTower(dcontainer);
	    }
	    break;
      default:
	    break;
    }
    return handled;
}

void createTower(ptrContainer & dcontainer)
{
    switch(dcontainer.curTower)
    {
      case 0:
      {
          slowtower* t = new slowtower(dcontainer.m->x*dcontainer.m->blockwidth,dcontainer.m->y*dcontainer.m->blockheight);
          dcontainer.towerlist->push_back((tower*)t);
          break;
      }
      case 1:
      {
          fasttower* t = new fasttower(dcontainer.m->x*dcontainer.m->blockwidth,dcontainer.m->y*dcontainer.m->blockheight);
          dcontainer.towerlist->push_back((tower*)t);
          break;
      }
      default:
        std::cout << "No tower selected, making default tower" << std::endl;
        dcontainer.towerlist->push_back(new tower(dcontainer.m->x*dcontainer.m->blockwidth,dcontainer.m->y*dcontainer.m->blockheight));
        break;
    }
    
}

double getDist(int x, int y, int xx, int yy)
{
    return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}

void drawAll(list<tower*> & l)
{
    for (list<tower*>::iterator it = l.begin(); it != l.end(); it++)
        (*it)->draw();
}
void drawAll(list<unit*> & l, double progress)
{
    for (list<unit*>::iterator it = l.begin(); it != l.end(); it++)
        (*it)->draw(progress);
}
void drawAll(list<missle*> & l)
{
    for (list<missle*>::iterator it = l.begin(); it != l.end(); it++)
        (*it)->draw();
}
void drawAll(list<particle*> & l, ptrContainer & dcontainer)
{
    for (list<particle*>::iterator it = l.begin(); it != l.end(); it++)
        (*it)->draw(dcontainer);
}
GLuint genTexArr(ptrContainer & dcontainer)
{
#if textureDebugTime == 1
    int start = SDL_GetTicks();
#endif
    unit::tex = loadGLTex("images/lemmling_Ladybug.png");
    unit1::tex = unit::tex;
    unit2::tex = loadGLTex("images/lemmling_Simple_cartoon_mouse.png");
    unit3::tex = loadGLTex("images/lughdailh_Taronjeru.png");
    tower::tex = unit::tex;
    slowtower::tex = loadGLTex("images/leftover_bacon_Camera_Lens.png");
    fasttower::tex = loadGLTex("images/mcol_atom.png");
    missle::tex = loadGLTex("images/mcol_bomb.png");
    gui::tex = loadGLTex("images/guibarbg.png");
    gui::numarr = new GLuint[128];
    
    for (int x = 1; x < 128; x++)
    {
        char ctoren = x;
        gui::numarr[x] = renderChar(ctoren);
    }

    dcontainer.towerTex = new GLuint[NUMOFTOWERS];
    dcontainer.towerTex[0] = slowtower::tex;
    dcontainer.towerTex[1] = fasttower::tex;

    dcontainer.towerNames = new std::string[NUMOFTOWERS];
    dcontainer.towerNames[0] = "Slow Tower";
    dcontainer.towerNames[1] = "Fast Tower";
    
    dcontainer.particleTex = new GLuint[1];
    dcontainer.particleTex[0] = loadGLTex("images/smoke.png");
    dcontainer.particleTex[1] = loadGLTex("images/explosion.png");

#if textureDebugTime == 1
    std::cout << "Texture Loading Time: " << SDL_GetTicks() - start << "ms." << std::endl;
#endif



#if soundDebugTime == 1
    int start2 = SDL_GetTicks();
#endif
    dcontainer.sounds = new Mix_Chunk*[NUMOFSOUNDS];
    dcontainer.sounds[0] = Mix_LoadWAV("sound/183__plagasul__1.wav");
    dcontainer.sounds[1] = Mix_LoadWAV("sound/31874__HardPCM__Chip037.wav");
    dcontainer.sounds[2] = Mix_LoadWAV("sound/31875__HardPCM__Chip038.wav");
    
   
#if soundDebugTime == 1
    std::cout << "Sound Loading Time: " << SDL_GetTicks() - start2 << "ms." << std::endl;
#endif

    return loadGLTex("images/background.png");
}
void drawBackground(GLuint tex)
{
    glPushMatrix();
    glTranslatef(0,0,0);
    glBindTexture(GL_TEXTURE_2D, tex);
    glBegin(GL_QUADS);
    glTexCoord2i(0,0); //Texture coord
    glVertex3f(0,0,0); //Point coord

    glTexCoord2i(1,0);
    glVertex3f(1024,0,0);
    
    glTexCoord2i(1,1);
    glVertex3f(1024,704,0);

    glTexCoord2i(0,1);
    glVertex3f(0,704,0);
    glEnd();
    glPopMatrix();
    glLoadIdentity();
}

void updateTowerTargets(ptrContainer & dcontainer, int ms)
{
///Updates the tower's target if it is targeting NULL, then fires weapons if the weapon has cooled down
    for (list<tower*>::iterator it = dcontainer.towerlist->begin(); it != dcontainer.towerlist->end(); it++)
    {
        if ((*it)->firecd > 0) //0 = ready to fire
        {
            (*it)->firecd -= ms;
        }
        (*it)->updateTarget(*dcontainer.unitlist);
        if ((*it)->target != NULL && (*it)->firecd <= 0)
        {
            //reset cooldown
            (*it)->firecd = (*it)->maxfirecd;
            //play sound
            Mix_PlayChannel(-1, dcontainer.sounds[0], 0);
            //spawn missles
            missle* tempm = new missle((*it)->x + (*it)->width/2, (*it)->y + (*it)->height/2);
            tempm->target = (*it)->target;
            dcontainer.misslelist->push_back(tempm);
        }
    }
}

void moveAllUnits(list<unit*> & unitlist)
{
    //Move all units to the next space. Called only once per tick to move to the next block
    for (list<unit*>::iterator it = unitlist.begin(); it != unitlist.end(); it++)
    {
        (*it)->nextSpace();
    }
}

void grimReaper(ptrContainer & dcontainer, int spawns)
{
    list<unit*>::iterator it = dcontainer.unitlist->begin();
    while (it != dcontainer.unitlist->end())
    {
        //get the next item in the list
        list<unit*>::iterator tempit = it;
        tempit++;

        //If it has no health left move it far away so towers retarget... will get destroyed next pass
        if ((*it)->hp < 0)
        {
            (*it)->x = 4200;
            (*it)->y = 4200;
            Mix_PlayChannel(-1, dcontainer.sounds[1], 0);
        }
        //If the enemy is out of bounds of the screen, this is true after the unit is moved to the obscenely far out position of 4200,4200
        if ((*it)->x > 1050 && (*it)->y > 800)
        {


            //Handle Money grants from unit kills
            if ((*it)->hp <= 0)
            {
                dcontainer.theplayer->grantMoney((*it)->bounty);
                stringstream str;
                str << "+" << (*it)->bounty;
                textparticle* tmp = new textparticle(str.str() /*tex*/, 3/*duration*/, 0/*transparency*/, 930/*x*/, 699 /*y*/, 64/*width*/, 32 /*height*/, 0 /*angle*/, 0.4 /*change transparency*/, 0/*change x*/, -50/*change y*/, 0/*change width*/, 0 /*change height*/, 0/*change angle*/, 0 /*red*/, 1/*green*/, 0/*blue*/, 0 /*change red*/, 0 /*change green*/, 0 /*change blue*/);
                dcontainer.particlelist->push_back((particle*)tmp);
            }
	    
            //Go through every tower and check to see if it is targeting the unit... this is a waste since it is out of range but still a good check anyway
            for (list<tower*>::iterator tit = dcontainer.towerlist->begin(); tit != dcontainer.towerlist->end(); tit++)
            {
                if ((*tit)->target == (*it))
                {
                    (*tit)->target = NULL;
                }
            }
            //iterate through missles and remove any that we're targeting dead enemy
            list<missle*>::iterator mit = dcontainer.misslelist->begin();
            while ( mit != dcontainer.misslelist->end())
            {
                list<missle*>::iterator nmit = mit;
                nmit++;
                if ((*mit)->target == (*it))
                {
                    delete (*mit);
                    dcontainer.misslelist->erase(mit);
                    //(*mit)->target = NULL;
                }
                mit = nmit;
            }
            //Delete the actual unit
            unit* addr = (*it);
            dcontainer.unitlist->erase(it);
            delete addr;
        }
        //If enemy has reached the end square move him far away so towers retarget.... this is a bug fix
        if ((*it)->x == 992 && (*it)->y == 672)
        {
	    
            (*it)->x = 4200;
            (*it)->y = 4200;
            dcontainer.theplayer->curhp -= spawns;
        }

        //continue on to the next one in the list
        it=tempit;
    }


}

void wave(ptrContainer & dcontainer)
{

    static list<int> retpath;
    static int enemyLeft = 0;
    static int waveind = -1;
    static int numArr[numOfWaveFunc] = {10,20,5};
    static int numOfEnemies = 0;

    typedef unit* (*wavept)(list<int>&);
    wavept wavearr[numOfWaveFunc] = {&wave1, &wave2, &wave3};

    //This if statement is true if the previous wave ended and a new wave is beginning... this is called to regenerate the 
    if (dcontainer.g->updateTime(0))
    {
        retpath.clear();
        if (!dcontainer.p->getPath(0,0,31,21,retpath))
        {
            badtouch(dcontainer);
            retpath.clear();
            dcontainer.p->getPath(0,0,31,21,retpath);
        }
        waveind++;
        if (waveind > numOfWaveFunc-1)
        {//NORMALLY would be victory condition, but loop around for now
            waveind = 0;
        }
        numOfEnemies = numArr[waveind];
        dcontainer.g->updateTime(-TIME_BETWEEN_WAVES);
    }

    //Add enemies to the queue
    enemyLeft += numOfEnemies;
    numOfEnemies = 0;

    if (enemyLeft > 0)
    {
        dcontainer.unitlist->push_back((*wavearr[waveind])(retpath));
        enemyLeft--;
    }
}

//Returns the angle in degree between 2 points
double getAng(int x, int y, int xx, int yy)
{
    return atan2(yy-y,xx-x) * 180 / PI;
}

//Move all missles towards their target... per is a decimal percentage of how much of their speed it should move
//ALSO FUCKING DAMAGES THE FUCKING THINGS, FUCKER
void moveAll(list<missle*> & misslelist, double per, ptrContainer & dcontainer)
{
    list<missle*>::iterator it = misslelist.begin();
    while (it != misslelist.end())
    {
        list<missle*>::iterator next = it;
        next++;

        //The move function returns true if it hits its target
        if ((*it)->move(per, dcontainer))
        {
            //subtract missles dmg from enemy's hp
            (*it)->target->hp -= (*it)->dmg;
            //Delete missle on the heap
            delete (*it);
            //Remove missle from misslelist
            misslelist.erase(it);
        }
        //Continue to the next missle in the list
        it = next;
    }
}

int nextpoweroftwo(int x)
{
    double logbase2 = log(x) / log(2);
    return round(pow(2,ceil(logbase2)));
}

void populateStaticVars()
{
    //tower::bcost = GENERIC_TOWER_COST;
}
void badtouch(ptrContainer & dcontainer)
//needs to be random, deletes ALL towers atm
{
    list<tower*>::iterator it = dcontainer.towerlist->begin();
    while (it != dcontainer.towerlist->end())
    {
        list<tower*>::iterator next = it;
        next++;
        deleteTower(dcontainer, it);
        it = next;
    }
}

void deleteTower(ptrContainer & dcontainer, list<tower*>::iterator rm)
{
    for (int cury = (*rm)->y/dcontainer.m->blockheight; cury <= (*rm)->y/dcontainer.m->blockheight+1; cury++)
    {
        for (int curx = (*rm)->x/dcontainer.m->blockwidth; curx <= (*rm)->x/dcontainer.m->blockwidth+1; curx++)
        {
            dcontainer.m->walkgrid[cury][curx] = true;
            dcontainer.p->data[cury][curx].setw(true);
        }
    }
    delete (*rm);
    dcontainer.towerlist->erase(rm);

}
void updateParticles(ptrContainer & dcontainer, float tim)
{
    //std::cout << tim << std::endl;
    for (list<particle*>::iterator it = dcontainer.particlelist->begin(); it != dcontainer.particlelist->end(); it++)
    {
        (*it)->update(tim);
        if ((*it)->d < 0)
        {
            list<particle*>::iterator prev = it;
            prev--;
            delete (*it);
            dcontainer.particlelist->erase(it);
            it = prev;
        }
    }
}
